﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Caching;
using Catalyst.Caching;
using SubSonic;

namespace Catalyst
{
	public abstract class AbsObjectList<ItemType, ListType> : ActiveList<ItemType, ListType>, IDataRelational
		where ItemType : ActiveRecord<ItemType>,  new()
		where ListType : AbsObjectList<ItemType, ListType>, new()
	{
		//public T LoadAsRelation<T>(TableSchema.Table RelatedTableSchema, string ForeignKeyName, object PrimaryKeyValue)
		//{
		//    return LoadAsRelation<T>(RelatedTableSchema, ForeignKeyName, PrimaryKeyValue, null);
		//}

		public void LoadAsRelation<T>(TableSchema.Table RelatedTableSchema, string ForeignKeyName, object PrimaryKeyValue, HashSet<Aggregate> Aggregates)
		{
			BaseSchema = RelatedTableSchema;

			SqlQuery query = new Select()
			.From(BaseSchema)
			.Where(ForeignKeyName).IsEqualTo(PrimaryKeyValue);

			if (Aggregates != null && Aggregates.Count > 0)
			{
				foreach (Aggregate aggregate in Aggregates)
				{
					query.And(aggregate);
				}
			}

			LoadAndCloseReader(query.ExecuteReader());

			//return (T)Convert.ChangeType(this, GetType());
		}

		#region IDataRelational Members

		public abstract TableSchema.Table GetTableSchema();
		
		public void LoadAsRelation(TableSchema.Table RelatedTableSchema, string ForeignKeyName, object PrimaryKeyValue, HashSet<Aggregate> Aggregates)
		{
			LoadAsRelation<IDataRelational>(RelatedTableSchema, ForeignKeyName, PrimaryKeyValue, Aggregates);
		}

		public abstract void LoadRelations(List<string> RelationNames);

		#endregion

		private static readonly object _lockBaseSchema = new object();

		protected internal static TableSchema.Table table;

		/// <summary>
		/// The base static class that holds all schema info for the table.
		/// The class must be instanced at least once to populate this table.
		/// </summary>
		protected internal static TableSchema.Table BaseSchema
		{
			get
			{
				if (table == null)
				{
					lock (_lockBaseSchema)
							new ListType();
				}
				return table;
			}
			set { table = value; }
		}

		private Dictionary<string, OrderByDirection> cacheCollectionOrderBys;

		private string getAllCacheKey(Type Type)
		{
			return String.Format("{0}_All{1}", table.Provider.Name, SubSonic.Utilities.Utility.SingularToPlural(Type.Name));
		}

		private List<T> getAllItems<T>() where T : ItemType, new()
		{
			SqlQuery query = new Select().From(table);

			if (cacheCollectionOrderBys != null && cacheCollectionOrderBys.Count > 0)
			{
				foreach (var orderBy in cacheCollectionOrderBys)
				{
					switch (orderBy.Value)
					{
						case OrderByDirection.Desc: query.OrderDesc(orderBy.Key); break;
						default: query.OrderAsc(orderBy.Key); break;
					}
				}
			}

			ListType collection = new ListType();
			collection.Load<T>(query.ExecuteReader());

			return new List<T>(collection.Cast<T>());
		}

		private CacheSettings<List<T>> getAllCachedListItems<T>() where T : ItemType, new()
		{
			CacheSettings<List<T>> settings = new CacheSettings<List<T>>();
							
			SqlCacheDependency dependency = null;

			if (HttpContext.Current != null)
			{
				HashSet<string> cacheEnabledTables = new HashSet<string>(SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(table.Provider.DefaultConnectionString));
				foreach (string enabledTable in cacheEnabledTables)
				{
					if (enabledTable == table.TableName)
					{
						dependency = new SqlCacheDependency(table.Provider.Name, enabledTable);
						break;
					}
				}
			}

			settings.CachedObject = getAllItems<T>();
			settings.Dependency = dependency;

			return settings;
		}

		public ListType LoadAll<T>(Dictionary<string, OrderByDirection> OrderBys) where T : ItemType, new()
		{
			BaseSchema = GetTableSchema();

			cacheCollectionOrderBys = OrderBys;

			bool loadFromCache = false;

			if (HttpContext.Current != null)
			{
				HashSet<string> cacheEnabledTables = new HashSet<string>(SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(BaseSchema.Provider.DefaultConnectionString));
				foreach (string enabledTable in cacheEnabledTables)
				{
					if (enabledTable == BaseSchema.TableName)
					{
						loadFromCache = true;
						break;
					}
				}
			}

			List<ItemType> itemsToLoad = null;

			if (loadFromCache)
			{
				List<T> allItems = CachingService.GetCachedObject(getAllCacheKey(typeof(ItemType)), new CachingService.ObjectLoader<List<T>>(getAllCachedListItems<T>));
                
				itemsToLoad = Cloning.CloneHelper<List<ItemType>>.Clone(new List<ItemType>(allItems.Cast<ItemType>()));
				
			}
			else
			{
				itemsToLoad = new List<ItemType>(getAllItems<T>().Cast<ItemType>());
			}

			AddRange(itemsToLoad);

			return (ListType)this;
		}

		public ListType LoadAll<T>() where T : ItemType, new()
		{
			return LoadAll<T>(null);
		}

		public virtual ListType LoadAll(Dictionary<string, OrderByDirection> OrderBys)
		{
			return LoadAll<ItemType>(OrderBys);
		}

		public virtual ListType LoadAll()
		{
			return LoadAll<ItemType>();
		}

		public void ClearCache()
		{
			//LoadSchema();

			BaseSchema = GetTableSchema();

			HttpContext.Current.Cache.Remove(getAllCacheKey(typeof(ItemType)));			
		}

		public ListType ReloadAll()
		{
			return ReloadAll<ItemType>();
		}

		public ListType ReloadAll<T>() where T : ItemType, new()
		{
			ClearCache();
			
			return LoadAll<T>();
		}

		public abstract void Load(TableSchema.Table Table, IDataReader Reader);

		//public abstract ListType Load(IDataReader Reader);

		public abstract void Load<T>(IDataReader Reader) where T : ItemType, new();

		public abstract void Load(TableSchema.Table TableSchema, DataTable DataTable);

		//public abstract ListType Load(DataTable TableSchema);
	}
}
